﻿// ***********************************************************************************************************************************************************
// * Copyright (c) 2008 Unit Software                                                                                                                        *
// *                                                                                                                                                         *
// * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),      *
// * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,      *
// * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:              *
// *                                                                                                                                                         *
// * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.                          *
// *                                                                                                                                                         *
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,     *
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER      *
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS    *
// * IN THE SOFTWARE.                                                                                                                                        *
// ***********************************************************************************************************************************************************
using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Reflection;
using System.Transactions;

namespace UnitSoftware.LINQ2SQLExtensions
{
    public class DataContextOperationStack
    {
        private readonly Queue<EntityOperation> _operationQueue = new Queue<EntityOperation>();

        internal readonly Dictionary<DataContext, Dictionary<Guid, Entity>> DeserializedNewEntities =
            new Dictionary<DataContext, Dictionary<Guid, Entity>>();

        public bool HasOperations
        {
            get { return _operationQueue.Count > 0; }
        }

        public void Clear()
        {
            _operationQueue.Clear();
        }

        internal void StackOperation(Entity entity, Operation operation)
        {
            switch (operation)
            {
                case Operation.Insert:
                    StackOperation(new InsertEntityOperation(entity));
                    break;
                case Operation.Delete:
                    StackOperation(new DeleteEntityOperation(entity));
                    break;
            }
        }

        internal void StackOperation(EntityOperation operation)
        {
            _operationQueue.Enqueue(operation);
        }

        public DataChanges Execute(ExtendedDataContext dataContext)
        {
            DataChanges changes;

            using (var ts = new TransactionScope())
            {
                while (_operationQueue.Count > 0)
                    _operationQueue.Dequeue().Execute(dataContext, this);

                changes = dataContext.SubmitChanges();

                ts.Complete();
            }

            return changes;
        }

        #region Nested type: EntityOperation

        public abstract class EntityOperation
        {
            public Entity OperationEntity;

            protected EntityOperation(Entity entity)
            {
                OperationEntity = entity;
            }

            public abstract void Execute(DataContext context, DataContextOperationStack stack);
        }

        #endregion

        #region Nested type: DeleteEntityOperation

        public class DeleteEntityOperation : EntityOperation
        {
            public DeleteEntityOperation(Entity entity)
                : base(entity)
            {
            }

            public override void Execute(DataContext context, DataContextOperationStack stack)
            {
                context.GetTable(OperationEntity.TableType).DeleteOnSubmit(OperationEntity);
            }
        }

        #endregion

        #region Nested type: InsertEntityOperation

        public class InsertEntityOperation : EntityOperation
        {
            public InsertEntityOperation(Entity entity)
                : base(entity)
            {
            }

            public override void Execute(DataContext context, DataContextOperationStack stack)
            {
                context.GetTable(OperationEntity.TableType).InsertOnSubmit(
                    stack.DeserializedNewEntities[context][(OperationEntity).TransientGuid]);
            }
        }

        #endregion

        #region Nested type: NewEntityOperation

        public class NewEntityOperation<TEntity> : EntityOperation where TEntity : Entity
        {
            public NewEntityOperation(TEntity entity)
                : base(entity)
            {
            }

            public override void Execute(DataContext context, DataContextOperationStack stack)
            {
                Dictionary<Guid, Entity> entities;
                if (!stack.DeserializedNewEntities.ContainsKey(context))
                {
                    entities = new Dictionary<Guid, Entity>();
                    stack.DeserializedNewEntities.Add(context, entities);
                }
                else
                {
                    entities = stack.DeserializedNewEntities[context];
                }

                var entity =
                    (TEntity)
                    Assembly.GetAssembly(OperationEntity.GetType()).CreateInstance(OperationEntity.GetType().FullName);

                if (!entities.ContainsKey(OperationEntity.TransientGuid))
                    entities.Add(OperationEntity.TransientGuid, entity);
            }
        }

        #endregion

        #region Nested type: PropertyChangedOperation

        public class PropertyChangedOperation : EntityOperation
        {
            public readonly PropertyInfo PropertyInfo;
            public object Value;

            public PropertyChangedOperation(Entity entity, string propertyName)
                : base(entity)
            {
                Value =
                    (PropertyInfo = entity.GetType().GetProperties().Single(p => p.Name == propertyName)).GetValue(
                        entity, new object[] {});
            }

            public override void Execute(DataContext context, DataContextOperationStack stack)
            {
                Entity contextualEntity;

                if ((contextualEntity = OperationEntity.GetOnContext(context)) != null)
                    OperationEntity = contextualEntity;
                else
                    OperationEntity = stack.DeserializedNewEntities[context][OperationEntity.TransientGuid];

                if (Value is Entity)
                {
                    if ((contextualEntity = ((Entity) Value).GetOnContext(context)) != null)
                        Value = contextualEntity;
                    else
                        Value = stack.DeserializedNewEntities[context][((Entity) Value).TransientGuid];
                }

                PropertyInfo.SetValue(OperationEntity, Value, new object[] {});
            }
        }

        #endregion
    }
}